/**
 * Copyright (c) 2021-2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "plugins/ecmascript/runtime/base/string_helper.h"
#include "plugins/ecmascript/runtime/ecma_string-inl.h"

namespace ark::ecmascript::base {
std::string StringHelper::ToStdString(EcmaString *string)
{
    return std::string(ConvertToPandaString(string));
}

bool StringHelper::CheckDuplicate(EcmaString *string)
{
    if (string->IsUtf8()) {
        const uint8_t *array = string->GetDataUtf8();
        size_t length = string->GetUtf8Length() - 1;
        std::bitset<UINT8_MAX> bitSet;
        for (size_t i = 0; i < length; ++i) {
            char idx = *array;
            if (bitSet.test(idx)) {
                return true;
            }
            bitSet.set(idx);
            array++;  // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
        }
    } else {
        UNREACHABLE();
    }
    return false;
}

EcmaString *StringHelper::Repeat(JSThread *thread, const std::u16string &thisStr, int32_t repeatLen, bool canBeCompress)
{
    ecmascript::ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
    if (repeatLen == 0) {
        return *factory->GetEmptyString();  // Create empty EcmaString.
    }
    std::u16string tmpStr = thisStr;
    for (int32_t i = 1; i < repeatLen; i++) {
        tmpStr.append(thisStr);
    }
    const char16_t *constChar16TData = tmpStr.data();
    auto *char16TData = const_cast<char16_t *>(constChar16TData);
    auto *uint16TData = reinterpret_cast<uint16_t *>(char16TData);
    int32_t length = tmpStr.size();
    return *factory->NewFromUtf16UnCheck(uint16TData, length, canBeCompress);
}

EcmaString *StringHelper::Trim(JSThread *thread, const std::u16string &thisStr, TrimKind kind)
{
    [[maybe_unused]] EcmaHandleScope handleScope(thread);
    ecmascript::ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
    std::u16string tmpStr = thisStr;
    if (tmpStr.empty()) {
        return *factory->GetEmptyString();
    }
    std::string str = U16stringToString(tmpStr);
    std::wstring wstr = StringToWstring(str);
    std::wregex r;
    if (kind == TrimKind::TRIM_START) {
        r =
            (L"^["
             L"\u0009\u000A\u000B\u000C\u000D\u0020\u00A0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007"
             L"\u2008\u2009\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF]+");
    } else if (kind == TrimKind::TRIM_END) {
        r =
            (L"["
             L"\u0009\u000A\u000B\u000C\u000D\u0020\u00A0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007"
             L"\u2008\u2009\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF]+$");
    } else {
        ASSERT(kind == TrimKind::TRIM_START_END);
        r =
            (L"^["
             L"\u0009\u000A\u000B\u000C\u000D\u0020\u00A0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007"
             L"\u2008\u2009\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF]+|["
             L"\u0009\u000A\u000B\u000C\u000D\u0020\u00A0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007"
             L"\u2008\u2009\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF]+$");
    }
    wstr = regex_replace(wstr, r, L"");
    str = WstringToString(wstr);
    tmpStr = StringToU16string(str);
    const char16_t *constChar16TData = tmpStr.data();
    auto *char16TData = const_cast<char16_t *>(constChar16TData);
    auto *uint16TData = reinterpret_cast<uint16_t *>(char16TData);
    int32_t length = tmpStr.size();
    return *factory->NewFromUtf16(uint16TData, length);
}

// ES2021 22.1.3.15.1 StringPad ( O, max_length, fill_string, placement )
ark::ecmascript::JSTaggedValue StringHelper::StringPad(JSThread *thread, JSHandle<JSTaggedValue> obj,
                                                       JSHandle<JSTaggedValue> maxLength,
                                                       JSHandle<JSTaggedValue> fillString, PadPlacement placement)
{
    [[maybe_unused]] EcmaHandleScope handleScope(thread);
    // 1. Assert: placement is start or end.
    ASSERT(placement == PadPlacement::START || placement == PadPlacement::END);

    // 2. Let S be ? ToString(O).
    JSHandle<EcmaString> string = JSTaggedValue::ToString(thread, obj);
    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);

    // 4. Let string_length be the length of S.
    size_t stringLength = string->GetLength();

    // 3. Let int_max_length be ? ToLength(max_length).
    size_t intMaxLength = JSTaggedValue::ToLength(thread, maxLength).ToUint32();
    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);

    // 5. If int_max_length ≤ string_length, return S.
    if (intMaxLength <= stringLength) {
        return string.GetTaggedValue();
    }

    // 6. If fill_string is undefined, let filler be the String value consisting solely of the code unit 0x0020 (SPACE).
    // 7. Else, let filler be ? ToString(fill_string).
    ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
    JSHandle<EcmaString> filler;
    if (fillString->IsUndefined()) {
        filler = factory->NewFromString(" ");
    } else {
        filler = JSTaggedValue::ToString(thread, fillString);
        RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
    }

    // 8. If filler is the empty String, return S.
    size_t fillerLength = filler->GetLength();
    if (fillerLength == 0) {
        return string.GetTaggedValue();
    }

    // 9. Let fill_len be int_max_length - string_length.
    uint32_t fillLen = intMaxLength - stringLength;

    // 10. Let truncated_string_filler be the String value consisting of repeated concatenations of filler truncated to
    // length fill_len.
    std::string truncatedString;
    std::string fillerString = ToStdString(filler.GetObject<EcmaString>());
    for (size_t i = 0; i < fillLen; i++) {
        truncatedString += fillerString[i % fillerLength];
    }
    JSHandle<EcmaString> truncatedStringFiller = factory->NewFromStdString(truncatedString);

    // 11. If placement is start, return the string-concatenation of truncated_string_filler and S.
    if (placement == PadPlacement::START) {
        return factory->ConcatFromString(truncatedStringFiller, string).GetTaggedValue();
    }

    // 12. Else, return the string-concatenation of S and truncated_string_filler.
    return factory->ConcatFromString(string, truncatedStringFiller).GetTaggedValue();
}
}  // namespace ark::ecmascript::base
